#include "util.hpp"
#include "fieldFunctions.hpp"
#include "meshTypeFunctions.hpp"

namespace hms
{


template<typename MeshType>
auto Field<MeshType>::domainValuesImp() -> decltype(auto) {
	return hms::domainValues(
		this->values(), this->mesh()
	);
}

template<typename MeshType>
auto Field<MeshType>::domainValuesImp() const -> decltype(auto) {
	return hms::domainValues(
		this->values(), this->mesh()
	);
}



namespace internal
{

template<WhichCells whichCells, typename FieldType>
auto values( FieldType&& f, const BoundaryPatch& patch ) -> decltype(auto)
{
	using F_unq = remove_qualifiers<FieldType>;
	using MeshType = typename F_unq::MeshType;
	using ValueType = std::remove_reference_t<decltype( f.values() )>;
	
	if constexpr ( isStructMeshBase<MeshType>() ){
		static constexpr Index ng {1};
		using BL = BoundaryLocation;
		BL loc { patch.location() };
		Index
			firstCell { patch.template cells<whichCells>()(0) },
			nComp     { f.nComponents() },
			stride    { nComp };
		if ( loc == BL::left || loc == BL::right ){
			Index nxg { 2 * ng + f.mesh().nCellsX() };
			stride *= nxg;
		}
		using Eigen::OuterStride;
		return Map<ValueType,Eigen::Unaligned,OuterStride<>>(
			f.values().col(firstCell).data(),
			nComp, patch.size(),
			OuterStride<>( stride )
		);
	} else {
		return f.values()( hms::all, patch.template cells<whichCells>() );
	}
}

} // namespace internal



template<typename MeshType>
auto Field<MeshType>::domainValues( const BoundaryPatch& patch ) const ->
	decltype(auto)
{
	return internal::values<WhichCells::domain>( *this, patch );
}

template<typename MeshType>
auto Field<MeshType>::domainValues( const BoundaryPatch& patch ) ->
	decltype(auto)
{
	return internal::values<WhichCells::domain>( *this, patch );
}

template<typename MeshType>
auto Field<MeshType>::ghostValues( const BoundaryPatch& patch ) const ->
	decltype(auto)
{
	return internal::values<WhichCells::ghost>( *this, patch );
}


template<typename MeshType>
auto Field<MeshType>::ghostValues( const BoundaryPatch& patch ) ->
	decltype(auto)
{
	return internal::values<WhichCells::ghost>( *this, patch );
}


template<typename MeshType>
typename std::vector<BoundaryPatch>::difference_type getPatchIndex(
	const MeshBase<MeshType>& mesh,
	const BoundaryPatch& patch
){
	auto nPatches { mesh.boundaryPatches().size() };
	using diff_type = typename std::vector<BoundaryPatch>::difference_type;
	diff_type patchIndex {
		std::distance( mesh.boundaryPatches().data(), &patch )
	};

	if ( patchIndex < 0 ||
		patchIndex >= static_cast<diff_type>(nPatches)
	){
		throw MeshError(
			  "Boundary patch index i = "
			+ std::to_string(patchIndex) + " out of bounds! "
			+ "Index must not be negative and must be smaller than "
			+ std::to_string(nPatches) + "."
		);
	}

	return patchIndex;
}


template<typename MeshType>
template<typename BCType, typename ... Ts>
auto Field<MeshType>::setBoundaryCondition(
	const BoundaryPatch& patch, Ts&& ... args
)
	-> BCType&
{
	return setBoundaryCondition( BCType{
		*this, patch, std::forward<Ts>(args) ...
	} );
}


template<typename MeshType>
template<typename BCType>
auto Field<MeshType>::setBoundaryCondition( BCType&& bc ) -> BCType& {
	static_assert( std::is_base_of_v<BoundaryCondition<MeshType>, BCType>,
	"Passed-in object type not derived from BoundaryCondition class!" );

	resizeBCsToPatchesSize();

	assert( &( bc.field() ) == this );
	const BoundaryPatch& patch { bc.patch() };

	auto patchIndex { getPatchIndex( this->mesh(), patch) };

	m_boundaryConditions[patchIndex] = std::make_unique<BCType>(
		std::move(bc)
	);
	return static_cast<BCType&>( *(m_boundaryConditions[patchIndex]) );
}

} // end namespace hms